﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.Windows.Forms.Integration;
using System.Windows.Controls.Primitives;
using System.ComponentModel;

namespace VncTab
{
	public partial class MainWindow : Window
	{
		[System.Runtime.InteropServices.DllImport("user32.dll")]
		private static extern bool SetForegroundWindow(IntPtr hWnd);
		private readonly ObservableCollection<VncItem> tabsSource = new ObservableCollection<VncItem>();
		private readonly static Dictionary<string, string> defaultArguments = new Dictionary<string, string>
		{
			{ "/r", "false" },
			{ "/t", "0" },
			{ "/l", "0" },
			{ "/w", "1000" },
			{ "/h", "800" },
			{ "/f", "false" }
		};

		public MainWindow()
		{
			this.InitializeComponent();
			this.vncTabs.ItemsSource = this.tabsSource;
		}

		public void HandleArguments(string[] arguments)
		{
			Dispatcher.BeginInvoke((Action)(() =>
			{
				IDictionary<string, string> parseArguments = ParseArguments(arguments);
				if (parseArguments == null)
				{
					if (this.tabsSource.Count <= 0)
					{
						Application.Current.Shutdown();
					}
					return;
				}

				CreateDesktop(parseArguments);
				this.Top = double.Parse(parseArguments["/t"]);
				this.Left = double.Parse(parseArguments["/l"]);
				this.Width = Math.Max(400d, double.Parse(parseArguments["/w"]));
				this.Height = Math.Max(300d, double.Parse(parseArguments["/h"]));
				this.WindowState = bool.Parse(parseArguments["/f"]) ? System.Windows.WindowState.Maximized : System.Windows.WindowState.Normal;
				this.Topmost = true;
				SetForegroundWindow(System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle);
				this.Topmost = false;
			}));
		}

		private UIElement CreateErrorControl(string content, VncItem item)
		{
			var stack = new StackPanel();
			stack.Children.Add(new TextBlock
			{
				Text = content,
				TextWrapping = TextWrapping.Wrap,
				Margin = new Thickness(10),
				VerticalAlignment = VerticalAlignment.Top,
				HorizontalAlignment = HorizontalAlignment.Left,
			});
			var grd = new UniformGrid { Columns = 2, HorizontalAlignment = System.Windows.HorizontalAlignment.Center, Margin = new Thickness(0, 10, 0, 10), };
			var retry = new Button { Content = "Neu verbinden", Margin = new Thickness(0, 0, 2.5, 0), };
			retry.Click += (s, args) => Dispatcher.BeginInvoke((Action)(() => this.CreateDesktop(item.Parameters)));
			var close = new Button { Content = "Schließen", Margin = new Thickness(2.5, 0, 0, 0), };
			close.Click += (s, args) => Dispatcher.BeginInvoke((Action)(() =>
			{
				this.tabsSource.Remove(item);
				if (this.tabsSource.Count <= 0)
				{
					Application.Current.Shutdown();
				}
			}));
			grd.Children.Add(retry);
			grd.Children.Add(close);
			stack.Children.Add(grd);
			return stack;
		}

		private void CreateDesktop(IDictionary<string, string> parameters)
		{
			VncItem item = this.tabsSource.FirstOrDefault(existing => string.Equals(existing.Header, parameters["/d"], StringComparison.OrdinalIgnoreCase));
			if (item != null)
			{
				var cc = item.HostControl as ContentControl;
				if (cc != null)
				{
					var existingDesk = (cc.Content as WindowsFormsHost).Child as VncSharp.RemoteDesktop;
					if (existingDesk != null && existingDesk.IsConnected)
					{
						this.vncTabs.SelectedItem = item;
						return;
					}
				}
				this.tabsSource.Remove(item);
			}

			var desk = new VncSharp.RemoteDesktop();
			desk.ConnectionLost += this.desk_ConnectionLost;
			desk.GetPassword = () => parameters.ContainsKey("/p") ? parameters["/p"] : null;
			Dispatcher.BeginInvoke((Action)(() =>
			{
				try
				{
					item = new VncItem
					{
						HostControl = new ContentControl { Content = new WindowsFormsHost { Child = desk } },
						Parameters = parameters,
						Header = parameters["/d"],
					};
					desk.Connect(parameters["/d"], bool.Parse(parameters["/r"]), true);
				}
				catch (Exception e)
				{
					item = new VncItem
					{
						Parameters = parameters,
						Header = parameters["/d"],
					};
					item.HostControl = CreateErrorControl(string.Format("Unable to connect: {0}{1}", Environment.NewLine, e.Message), item);
				}

				this.tabsSource.Add(item);

				this.vncTabs.SelectedItem = item;
			}), System.Windows.Threading.DispatcherPriority.Background);
		}

		private void desk_ConnectionLost(object sender, EventArgs e)
		{
			var host = sender as VncSharp.RemoteDesktop;
			if (host != null)
			{
				var item = this.tabsSource.FirstOrDefault(existing => string.Equals(existing.Header, host.Hostname, StringComparison.OrdinalIgnoreCase));
				if (item != null)
				{
					item.HostControl = this.CreateErrorControl(
						string.Format("Die Verbindung zu {0} wurde unterbrochen. Soll die Verbindung wieder hergestellt werden?", host.Hostname),
						item);
				}
			}
		}

		private static IDictionary<string, string> ParseArguments(string[] arguments)
		{
			var tmp = arguments.ToDictionary(x => x.Substring(0, x.IndexOf('=')), x => x.Substring(x.IndexOf('=') + 1));
			foreach (var defaultArg in defaultArguments)
			{
				if (!tmp.ContainsKey(defaultArg.Key))
				{
					tmp[defaultArg.Key] = defaultArg.Value;
				}
			}

			if (!tmp.ContainsKey("/d") || string.IsNullOrWhiteSpace(tmp["/d"]))
			{
				MessageBox.Show("Argument /d=<hostname> is missing");
				return null;
			}

			return tmp;
		}

		private void Window_Closing(object sender, CancelEventArgs e)
		{
			e.Cancel = this.tabsSource.Count > 0;
		}

		private class VncItem : INotifyPropertyChanged
		{

			private UIElement hostControl;
			public UIElement HostControl
			{
				get { return hostControl; }
				set
				{
					hostControl = value;
					this.OnPropertyChanged(new PropertyChangedEventArgs("HostControl"));
				}
			}
			public string Header { get; set; }
			public IDictionary<string, string> Parameters { get; set; }
			public event PropertyChangedEventHandler PropertyChanged;
			protected virtual void OnPropertyChanged(PropertyChangedEventArgs args)
			{
				var handler = this.PropertyChanged;
				if (handler != null)
				{
					handler(this, args);
				}
			}
		}
	}
}